<template>
  <div class="file-upload">
    <div
      class="upload-area" :class="{'is-dragover': drag && isDragOver}"
      v-on="events"
    >
      <slot name="loading" v-if="isUploading">
        <button disabled>正在上传</button>
      </slot>
      <slot
        name="uploaded" v-else-if="lastFileData && lastFileData.loaded"
        :uploadedData="lastFileData.data"
      >
        <button>点击上传</button>
      </slot>
      <slot name="default" v-else>
        <button>点击上传</button>
      </slot>
    </div>
    <input
      ref="fileInput" type="file" :style="{ display: 'none' }"
      @change="handleFileChange"
    />
    <ul v-if="!autoUpload">
      <li
        :class="`uploaded-file upload-${file.status}`"
        v-for="file in fileList" :key="file.uid"
      >
        <img
          v-if="file.url && listType === 'picture'"
          class="upload-list-thumbnail"
          :src="file.url" :alt="file.name"
        >
        <span class="filename">{{ file.name }}</span>
        <button class="delete-icon" @click="removeFile(file.uid)">Del</button>
      </li>
    </ul>
  </div>
</template>

<script lang="ts" setup>
import { reactive, ref, computed, type PropType } from 'vue'
import axios from 'axios'
import { v4 as uuidv4 } from 'uuid'
type UploadStatus = 'ready' | 'loading' | 'success' | 'error'
type CheckUpload = (file:File) => boolean | Promise<File>
type TypeUploaded = (fileSrc:string) => void
export interface IUploadFile {
  uid: string
  size: number
  name: string
  status: UploadStatus
  raw: File
  resp?: any
  url: string
}
const props = defineProps({
  action: {
    type: String,
    required: true
  },
  beforeUpload: {
    type: Function as PropType<CheckUpload>
  },
  onUploaded: {
    type: Function as PropType<TypeUploaded>
  },
  drag: {
    type: Boolean,
    default: false
  },
  autoUpload: {
    type: Boolean,
    default: true
  },
  listType: {
    type: String as PropType<'picture' | 'text'>,
    default: 'text'
  }
})

const fileInput = ref<null | HTMLInputElement>(null)
const fileList = ref<IUploadFile[]>([])
const isDragOver = ref<boolean>(false)

const isUploading = computed(() => fileList.value.some(file => file.status === 'loading'))
const lastFileData = computed(() => {
  const len = fileList.value.length
  const lastFile = fileList.value[len-1]
  if (lastFile) {
    return {
      loaded: lastFile.status === 'success',
      data: lastFile.resp
    }
  }
  return false
})

const triggerUpload = () => {
  if(fileInput.value) {
    fileInput.value.click()
  }
}

const removeFile = (id: string) => {
  fileList.value = fileList.value.filter(file => file.uid !== id)
}

const postFile = (readyFile:IUploadFile) => {
  const formData = new FormData()
  formData.append(readyFile.name, readyFile.raw)
  readyFile.status = 'loading'
  axios.post(props.action, formData, {
    headers: { 'Content-Type': 'multipart/form-data' }
  }).then(res => {
    readyFile.status = 'success'
    readyFile.resp = res.data
    if(props.onUploaded) {
      console.log('props.onUploaded ... ');

      props.onUploaded(res.data?.url || readyFile.url)
    }
  }).catch(() => {
    readyFile.status = 'error'
  }).finally(() => {
    if (fileInput.value) {
      fileInput.value.value = ''
    }
  })
}

const addFileToList = (uploadedFile:File) => {
  const fileObj = reactive<IUploadFile>({
    uid: uuidv4(),
    size: uploadedFile.size,
    name: uploadedFile.name,
    status: 'ready',
    raw: uploadedFile,
    url: ''
  })
  if (props.listType === 'picture') {
    try {
      fileObj.url = URL.createObjectURL(uploadedFile)
    } catch (error) {
      console.error('upload file error: ', error)
    }
  }
  fileList.value.push(fileObj)
  if(props.autoUpload) {
    postFile(fileObj)
  }
}

const beforeUploadCheck = (files: null | FileList) => {
  if (files) {
    const uploadFile = files[0]
    if (props.beforeUpload) {
      const result = props.beforeUpload(uploadFile)
      if (result && result instanceof Promise) {
        result.then(processedFile => {
          if (processedFile instanceof File) {
            addFileToList(processedFile)
          } else {
            throw new Error('beforeUpload Promise should return File object')
          }
        }).catch(err => {
          console.error(err)
        })
      } else if (result === true) {
        addFileToList(uploadFile)
      }
    } else {
      addFileToList(uploadFile)
    }
  }
}

const uploadFiles = () => {
  fileList.value.filter(file => file.status === 'ready').forEach(readyFile => {
    postFile(readyFile)
  })
}

const handleFileChange = (e:any) => {
  const target = e.target as HTMLInputElement
  const files = target.files
  beforeUploadCheck(files)
}

const handleDrag = (e:DragEvent, over:boolean) => {
  e.preventDefault()
  isDragOver.value = over
}
const handleDrop = (e:DragEvent) => {
  e.preventDefault()
  isDragOver.value = false
  if(e.dataTransfer) {
    beforeUploadCheck(e.dataTransfer.files)
  }
}
let events:{ [key:string]: (e:any)=>void } = {
  'click': triggerUpload
}
if(props.drag) {
  events = {
    ...events,
    'dragover': (e:DragEvent) => handleDrag(e, true),
    'dragleave': (e:DragEvent) => handleDrag(e, false),
    'drop': handleDrop
  }
}

defineExpose({ uploadFiles })

</script>

<style lang="less" scoped>
.upload-loading {
  color: yellow;
}
.upload-success {
  color: green;
}
.upload-error {
  color: red;
}
.upload-area {
  width: 200px;
  height: 200px;
  display: flex;
  justify-content: center;
  align-items: center;
  border: 1px dashed #ccc;
  &.is-dragover {
    border: 2px dashed skyblue;
  }
}

.upload-list-thumbnail {
  width: 100px;
}
</style>